/* MOD.C
 * Modplayer routines, supports only S3M
 *************
 * 29.08.2000 : Toni R„s„nen
 *              modifications/documentation for open source
 *
 */

#include "mixer.h"
//#include "gfx2.h"
#include <stdio.h>
#include <stdarg.h>
#include <conio.h>
#include <stdlib.h>
#include "pakfile.h"

#define O_note          0
#define O_inst          1
#define O_volume        2
#define O_effect        3
#define O_info          4

struct s3m_headerTYPE { char const_1a;
                    char s3m_type;
                    short paskaa01;
                    short s3m_orders;
                    short s3m_instruments;
                    short s3m_patterns;
                    short s3m_flags;
                    short s3m_tracker;
                    short s3m_fileformat;
                    unsigned int s3m_formattag; // "SCRM"
                    char s3m_globvol;
                    char s3m_initspeed;
                    char s3m_inittempo;
                    char s3m_mastervol;
                    char s3m_ultraclick;
                    unsigned char s3m_defaultpan; // 252 -> pannings present
                    char paskaa02[8];
                    short s3m_special;
                    char s3m_pans[32]; };
struct s3m_instTYPE { char si_type;
                   char si_dosname[12];
                   char si_adx1;
                   short si_adx2;
                   int si_length;
                   int si_loopbeg;
                   int si_loopend;
                   char si_vol;
                   char si_paskaa1;
                   char si_packings;
                   char si_flags; // b0-loop, b1-stereo, b2-16bit
                   int si_c2spd;
                   int si_paskaa2;
                   short si_gp;
                   short si_512;
                   int si_lastused;
                   char si_samplename[28];
                   char si_tag[4]; }; // "SCRS"

char b;
int i;
int modvolume,sfxvolume;

FILE *tied;

char mod_name[28];
struct s3m_headerTYPE s3m_header;
struct s3m_instTYPE s3inst;

int mod_onepattsize;
char *mod_patt[100]; // patt,chn,row,pos
short mod_periods[12] = {1712*16,1616*16,1524*16,1440*16,1356*16,1280*16,1208*16,1140*16,1076*16,1016*16,960*16,907*16};
short s3m_parainst[100];
short s3m_parapatt[100];
short mod_instsize[100];
short mod_instc4[100];
unsigned char mod_orders[128];
short mod_instruments;
short mod_patterns;
short mod_length;
unsigned char pmod_speed,pmod_delay,pmod_tempo;
unsigned char pmod_initspeed;
char mod_channels;
unsigned char mod_balance[32];
char *mod_instsample[100];
unsigned char mod_instvolume[100];
int mod_instloop[100];
int mod_instend[100];
char mod_playing;
int mod_globalvolume;

char mod_vibradepth[32];
char mod_vibraspeed[32];

unsigned char pmod_note[32];
short pmod_period[32],pmod_oriperiod[32];
short pmod_slideto[32];
unsigned char pmod_inst[32];
char pmod_volume[32];
unsigned char pmod_play[32];
unsigned char mod_efinfo[32];
unsigned char mod_effect[32];
int pmod_setoffs[32];

int pmod_row,pmod_track,pmod_pattern;

// *********************************************************************
int mod_load (char *modname)
{ FILE *tied = ffopen ( modname,"rb");
  int xx,zz,i,size,ii;
  unsigned char *temp;
  char row,chn;
  unsigned short ssize;
  unsigned char b1,b2,b3;

//  printf ("loading s3m\n");
  fread (mod_name,1,28,tied);
  fread (&s3m_header,sizeof(s3m_header),1,tied);
//  printf ("%s\n",mod_name);

  mod_playing = 0;

  for (xx=0;xx<99;xx++)
    { s3m_parainst[xx]=s3m_parapatt[xx]=0; }
  mod_instruments = s3m_header.s3m_instruments;
  mod_patterns = s3m_header.s3m_patterns;
  mod_length = s3m_header.s3m_orders;

//  printf ("%d instruments, %d patterns, %d orders\n",mod_instruments,mod_patterns,mod_length);

  fread (&mod_orders,1,mod_length,tied);
    //orderxor -> mod_orders
  fread (&s3m_parainst,1,mod_instruments<<1,tied);
    //instxor ->
  fread (&s3m_parapatt,1,mod_patterns<<1,tied);

  pmod_initspeed = pmod_speed = pmod_delay = s3m_header.s3m_initspeed;
  pmod_tempo = s3m_header.s3m_inittempo;

  mod_channels = 0;
  for (i=0;i<31;i++)
    { mod_balance[i]= 0xff;
      zz = s3m_header.s3m_pans[i];
      if ((zz >= 0x00) && (zz <= 0x07))
        { mod_channels++; mod_balance[i] = 0x03; }
      if ((zz >= 0x08) && (zz <= 0x0F))
        { mod_channels++; mod_balance[i] = 0x0C; }
      if ((zz >= 0x80) && (zz <= 0x8F))
        { mod_channels++; mod_balance[i] = 0xFF; }
      if ( (s3m_header.s3m_mastervol & 0x80) == 0) { mod_balance[i] = 7; }
    }

  if ((s3m_header.s3m_defaultpan == 252) && ( (s3m_header.s3m_mastervol & 0x80) != 0))
    {
      for (i = 0; i<mod_channels;i++)
        { if ((mod_balance[i] < 0xe0) && (s3m_header.s3m_defaultpan & 32))
            { mod_balance[i] = s3m_header.s3m_defaultpan & 0x0f; }
        }
    }

  temp = (char*) malloc (65536);

  for (i=1;i<=mod_instruments;i++)
    if (s3m_parainst[i-1] != 0)
      {
//        printf ("inst %d\n", i);
        xx = s3m_parainst[i-1]; xx = xx * 16;
        ffseek (tied, xx, SEEK_SET);
        fread (&s3inst,1,sizeof(s3inst),tied);
        xx = s3inst.si_adx1;
        xx = ((xx<<16)+s3inst.si_adx2) * 16;
        ffseek (tied, xx, SEEK_SET);
        size = s3inst.si_length;
        mod_instsize[i] = size;
        mod_instc4[i] = s3inst.si_c2spd;

        if (size != 0)
          {  mod_instsample[i] = (char*) malloc(size+8);
             fread (mod_instsample[i],1,size,tied);
             zz = 0;
             mod_instvolume[i] = s3inst.si_vol;
             if (s3inst.si_flags & 1)
               {  mod_instloop[i] = s3inst.si_loopbeg-1;
                  if (mod_instloop[i] < 0) mod_instloop[i] = 0;
                  mod_instend[i] = s3inst.si_loopend-1;
               }
             else
               {  mod_instloop[i] = -1;
                  mod_instend[i] = size-1;
               }

             for (ii=0;ii<size;ii++)
               { *(mod_instsample[i]+ii) ^= 0x7F; }
             *(mod_instsample[i]+size) = 0;
             *(mod_instsample[i]+size+1) = 0;

/*             mix_volume[0] = 63;
             mix_playsound (0, mod_instsample[i],
                    0, mod_instloop[i], mod_instend[i], 8385);
             getch(); */

          } // if size!=0
        else
          { mod_instsample[i] = NULL; }
      }

//  printf ("patterns\n");

  mod_onepattsize = 64*5*mod_channels;
  for (i=0;i<=mod_patterns;i++)
    if (s3m_parapatt[i] != 0)
      { // printf ("pattern %d\n",i);
        mod_patt[i] = (char*) malloc (mod_onepattsize);
        xx = s3m_parapatt[i]; xx = xx*16;
        ffseek (tied, xx, SEEK_SET);
        fread (&ssize, 1,2,tied);
        fread (temp,1,ssize,tied);
        row = 0; ii = 0;
        for (ii=0;ii<mod_onepattsize;ii++)
          { *(mod_patt[i]+ii) = 0; }
        // (chn*320+row*5+data)
        ii = 0;
        while ((ii < ssize) && (row < 64))
          { b1 = *(temp+ii++);
            if (b1 == 0)
              { row++; } // row done
            else
              { chn = (b1 & 31); // row not done - get ch
                if (b1 & 32) // note/inst
                  { b2 = *(temp+ii++); //note
                    b3 = *(temp+ii++); //inst
                    if (b2 == 0xff) { b2 = 0; }
                    *(mod_patt[i]+((chn*320)+(row*5)+O_note)) = b2;
                    if (b3 == 0xff) { b3 = 0; }
                    *(mod_patt[i]+((chn*320)+(row*5)+O_inst)) = b3;
                  }
                if (b1 & 64) // volume
                  { b2 = *(temp+ii++); //get volume
                    if (b2 == 0) { b2 = 255; }
                    *(mod_patt[i]+((chn*320)+(row*5)+O_volume)) = b2;
                  }
                if (b1 & 128)
                  { b2 = *(temp+ii++); //eff
                    b3 = *(temp+ii++); //info
                    *(mod_patt[i]+((chn*320)+(row*5)+O_effect)) = b2;
                    *(mod_patt[i]+((chn*320)+(row*5)+O_info)) = b3;
                  }
              }
          }
      }
    else
      {  mod_patt[i] = NULL;  }

  free (temp);

  ffclose (tied);
  mod_playing = 1;
//  printf ("load!\n");
  return 0;
}

// *********************************************************************
void mod_release()
{  int i;
   for (i = 0; i <= mod_patterns; i++)
     if (mod_patt[i] != NULL)  free (mod_patt[i]);
   for (i = 1; i < mod_instruments; i++)
     if (mod_instsample[i] != NULL)  free (mod_instsample[i]);

   mod_playing = 0;
}



//************************************************
short makeperiod (int inst, int note)
{ int per,oct;
  if ((inst == 0) || (mod_instc4[inst] == 0)) { return(0); }
  oct = (note >> 4) & 0x0f;
  per = mod_periods[note & 0x0f];
  per = (8363*(per >> oct)) / mod_instc4[inst];
  return(per);

}

//**************************************************
int calcfreq(int period)
{ int i;
  i = 14317056 / period;
  return(i);
}

//********************************************************
void count_next_row ()
{ int chn,xx,torow;
  unsigned char note,inst,vol,eff,effx,effx2;

  torow = -1;
//  gotoxy (2,1); cprintf ("%2d",pmod_pattern);
  for (chn=0; chn < mod_channels; chn++)
    if (mod_balance[chn] < 0xe0)
      {
//        gotoxy (2,2); cprintf ("%d %d %d   ",pmod_track,pmod_row,chn);

        note = *(mod_patt[pmod_pattern]+((chn*320)+(pmod_row*5)+O_note));
        inst = *(mod_patt[pmod_pattern]+((chn*320)+(pmod_row*5)+O_inst));
//        if ((inst > 128) || (mod_instc4[inst] == 0)) { inst = 0; }
        vol = *(mod_patt[pmod_pattern]+((chn*320)+(pmod_row*5)+O_volume));
        eff = *(mod_patt[pmod_pattern]+((chn*320)+(pmod_row*5)+O_effect));
        effx = effx2 = *(mod_patt[pmod_pattern]+((chn*320)+(pmod_row*5)+O_info));

//        gotoxy (2,chn+3); cprintf ("%2X %2X %2X   ",note,inst,vol);

        // pmod_play: 01=inst, 02=note, 04=vol, 08=notecut, 10=setfreq, 20=setoffs
        if (inst != 0)
          { pmod_inst[chn] = inst;
            pmod_volume[chn] = mod_instvolume[inst];
            pmod_play[chn] |= 0x005; //inst&vol
          }

        if ((note != 0) && (eff != 7))
          { if (note == 0xfe)
              { pmod_play[chn] |= 0x008; }
            else
              { pmod_note[chn] = note;
                pmod_oriperiod[chn] = pmod_period[chn] = makeperiod (pmod_inst[chn],note);
                pmod_play[chn] |= 0x002;
              }
          }

        if (vol != 0)
          { if (vol == 255)
              { pmod_volume[chn] = 0; }
            else
              { pmod_volume[chn] = vol; }
            pmod_play[chn] |= 0x004;
          }

        if (effx)
          { mod_efinfo[chn] = effx; }
        else
          { effx = mod_efinfo[chn]; }
        mod_effect[chn] = eff;
        switch (eff)
          { case 0 : { break; }
              // 01: set speed
            case 1 : { pmod_speed = effx2; pmod_delay = effx2; break; }
              // 02: jump to order XX (as hex)
            case 2 : { pmod_track = effx2-1; torow = 0; break; }
              // 03: break pattern to row XX
            case 3 : { torow = effx2;
                       break;
                     }
              // 04: volumeslides
            case 4 : { if (((effx & 0xf0) == 0xf0) && ((effx & 0x0f) != 0))
                         { //finedown
                           pmod_volume[chn] -= (effx & 0x0f);
                           if (pmod_volume[chn] < 0) { pmod_volume[chn] = 0; }
                           mod_effect[chn] = 0;
                         } //finedown
                       if (((effx & 0x0f) == 0x0f) && ((effx & 0xf0) != 0))
                         { //fineup
                           pmod_volume[chn] += (effx >> 4);
                           if (pmod_volume[chn] > 64) { pmod_volume[chn] = 64; }
                           mod_effect[chn] = 0;
                         } //fineup
                       break;
                     }
              // 05: slide down
            case 5 : { if (((effx & 0xf0) == 0xf0) && ((effx & 0x0f) != 0))
                         { // Fx - fine down
                           pmod_period[chn] += (effx & 0x0f)*4;
                           pmod_oriperiod[chn] += (effx & 0x0f)*4;
                           pmod_play[chn] |= 0x010;
                           mod_effect[chn] = 0;
                         } // Fx - fine down
                       else
                         if (((effx & 0xf0) == 0xe0) && ((effx & 0x0f) != 0))
                           { // Ex - xtrafine down
                             pmod_period[chn] += (effx & 0x0f);
                             pmod_oriperiod[chn] += (effx & 0x0f);
                             pmod_play[chn] |= 0x010;
                             mod_effect[chn] = 0;
                           } // Ex - xtrafine
                       break;
                     }
              // 06: slide up
            case 6 : { if (((effx & 0xf0) == 0xf0) && ((effx & 0x0f) != 0))
                         { // Fx - fine down
                           pmod_period[chn] -= (effx & 0x0f)*4;
                           pmod_oriperiod[chn] -= (effx & 0x0f)*4;
                           pmod_play[chn] |= 0x010;
                           mod_effect[chn] = 0;
                         } // Fx - fine down
                       else
                         if (((effx & 0xf0) == 0xe0) && ((effx & 0x0f) != 0))
                           { // Ex - xtrafine down
                             pmod_period[chn] -= (effx & 0x0f);
                             pmod_oriperiod[chn] -= (effx & 0x0f);
                             pmod_play[chn] |= 0x010;
                             mod_effect[chn] = 0;
                           } // Ex - xtrafine
                       break;
                     }
              // 07: tone portamento
            case 7 : { xx = pmod_slideto[chn];
                       if ((note != 0) && (inst != 0))
                         { xx = makeperiod (inst, note); }
                       if ((note != 0) && (inst == 0))
                         { xx = makeperiod (pmod_inst[chn], note); }
                       if ((xx == 0) && (pmod_period[chn] == 0))
                         { mod_effect[chn] = 0; }
                       else
                         { pmod_slideto[chn] = xx; }
                       break;
                     }
              // 08: vibrato
            case 8 : { mod_vibraspeed[chn] = effx>>4;
                       mod_vibradepth[chn] = effx & 15;
                       break;
                     }
              // 09: tremor
              // 10: arpeggio
              // 11: dual: vibra+volslide
/*            case 11 : if (effx)
                        pmod_play[chn] |= 0x004;
                      break; */
              // 12: dual: toneporta+volslide
              // 15: set offs
            case 15: { pmod_setoffs[chn] = (effx << 8);
                       pmod_play[chn] |= 0x020;
                       break;
                     }
              // 17: retrigger
              // 18: tremolo
              // 19: s-commands
            case 19: { switch (effx >> 4)
                           // 0:filter ctrl
                       { case 0 : { break; }
                           // 1:glissado ctrl
                           // 2:finetune
                           // 3:vibra waveform
                           // 4:tremolo waveform
                           // 8:balance
                           //10:stereo ctrl
                           //11:loop
                           //12:notecut
                           //13:notedelay
                           //14:patterndelay
                           //15:funkrepeat
                       } //switch
                       break;
                     } //case19
              // 20:tempo
            case 20: { //set_mixbuffsize ( 22050 / ((pmod_tempo*4)/10) );
                       break;
                     }
              // 22:global volume
            case 22: { mod_globalvolume = effx;
                       break;
                     }
              // 24:x-balance
              // 25:vol (not a s3m-cmd)
          } //switch EFF

      }

  pmod_row++;
  if (torow != -1)
    { pmod_row = torow+64; }
  if (pmod_row > 63)
    {
      pmod_row = pmod_row - 64;
      chn = 0;
      while (chn == 0)
        { pmod_track++;
          if (pmod_track > mod_length) { pmod_track = 0; pmod_speed = pmod_initspeed; }
          pmod_pattern = mod_orders[pmod_track];
          if (pmod_pattern == 255) { pmod_track = -1; }
          if (pmod_pattern < 250) { chn = 1; }
        }
    }
}

char *partial_sample[32];
int partial_pos[32];
int partial_loop[32];
int partial_end[32];
int partial_freq[32];

void mix_playpartial_stream()
{  int chn,o,l;

   chn = intfromchannel;
   o = partial_pos[chn];

   l = partial_end[chn] - o;

   if (l > 1) // not in end
     {  if (partial_loop[chn] == -1) // not looping - stop endfunct
          sndendfunction[chn] = NULL;
        mix_playsound (chn, partial_sample[chn]+o, 0, -1, l, partial_freq[chn]);
        partial_pos[chn] += l;
     }
   else
     {  // end reached
        if (partial_loop[chn] != -1) // looping sample
          {  // because we're here, we can assume that sample is longer than 32700bytes
             partial_pos[chn] = 32700;

             mix_playsound (chn, partial_sample[chn], 0, -1, 32700, partial_freq[chn]);
          }
        else
          mix_speed[chn] = 0; // end + not looping -> STOP!
     }

}

void mix_playpartial(int chn, char *sam, int beg, int loop, int end, int freq)
{  if (end < 32700)
     {  // ok
        sndendfunction[chn] = NULL;
        mix_playsound (chn, sam, beg, loop, end, freq);
     }
   else
     {  mix_playsound (chn, sam, beg, -1, 32700, freq);
        sndendfunction[chn] = mix_playpartial_stream;
        partial_sample[chn] = sam;
        partial_pos[chn] = 32700;
        partial_loop[chn] = loop;
        partial_end[chn] = end;
        partial_freq[chn] = freq;
     }
}

//*******************************************
void play_line()
{ int chn,inst,per,freq;
  unsigned int l;

  for (chn=0; chn < mod_channels; chn++)
    if (mod_balance[chn] < 0xe0)
      { if (pmod_play[chn] & 0x004)
          {  //mix_volume[chn] = pmod_volume[chn];
             mix_setvolume (chn,pmod_volume[chn] * modvolume / 64);
          }

        if (pmod_play[chn] & 0x003) // INST AND/OR NOTE
          { inst = pmod_inst[chn];
            per = pmod_period[chn];
            if ((inst != 0) && (per != 0))
              {  freq = calcfreq (per);
                 l = 0;
                 if (pmod_play[chn] & 0x020)
                   {  l += pmod_setoffs[chn]<<8;
                      pmod_play[chn] &= (~0x020);
                   }

                 mix_playpartial(chn, mod_instsample[inst],
                                     l, mod_instloop[inst], mod_instend[inst], freq);
                 mix_playsound (chn, mod_instsample[inst],
                                     l, mod_instloop[inst], mod_instend[inst], freq);
              }
          }



        if (pmod_play[chn] & 0x008)
          {  mix_speed[chn] = 0;
          }

        if ((pmod_play[chn] & 0x010) && (pmod_period[chn] != 0))
          {  freq = calcfreq (pmod_period[chn]);
             mix_setfreq (chn, freq);
          }
        if (pmod_play[chn] & 0x020)
          {  mix_adx[chn] = (unsigned int)pmod_setoffs[chn]<<15;
          }
      }
}

//*****************************************************
void do_betweencmd()
{ int chn,i;
  for (chn=0; chn <mod_channels;chn++)
    { switch (mod_effect[chn])
        {   //04: volumeslides
          case 4 : { if ( (mod_efinfo[chn] & 0xf0) == 0)
                       { i = -(mod_efinfo[chn] & 0x0f); }
                     else
                       { i = (mod_efinfo[chn] >> 4) & 0x0f; }
                     pmod_volume[chn] += i;
                     if (pmod_volume[chn] > 64) { pmod_volume[chn] = 64; }
                     if (pmod_volume[chn] < 0) { pmod_volume[chn] = 0; }
                     pmod_play[chn] |= 0x004;
                     break;
                   }
            //05:  slide down
          case 5 : { pmod_period[chn] += mod_efinfo[chn]*4;
                     pmod_oriperiod[chn] += mod_efinfo[chn]*4;
                     pmod_play[chn] |= 0x010;
                     break;
                   } //slidedown
            //06:  slide up
          case 6 : { pmod_period[chn] -= mod_efinfo[chn]*4;
                     pmod_oriperiod[chn] -= mod_efinfo[chn]*4;
                     pmod_play[chn] |= 0x010;
                     break;
                   } //slideup
            //07:  tone porta
          case 7 : { if (pmod_period[chn] < pmod_slideto[chn])
                       { pmod_period[chn] += mod_efinfo[chn]*4;
                         pmod_oriperiod[chn] += mod_efinfo[chn]*4;
                         if (pmod_period[chn] >= pmod_slideto[chn])
                           { pmod_oriperiod[chn] = pmod_period[chn] = pmod_slideto[chn]; }
                         pmod_play[chn] |= 0x010;
                       }
                     if (pmod_period[chn] > pmod_slideto[chn])
                       { pmod_period[chn] -= mod_efinfo[chn]*4;
                         pmod_oriperiod[chn] -= mod_efinfo[chn]*4;
                         if (pmod_period[chn] <= pmod_slideto[chn])
                           { pmod_oriperiod[chn] = pmod_period[chn] = pmod_slideto[chn]; }
                         pmod_play[chn] |= 0x010;
                       }
                     break;
                   }
            //08: vibrato
          case 8 : {
                     break;
                   }
           //dual: vibra + volslide
          case 11: { if ( (mod_efinfo[chn] & 0xf0) == 0)
                       { i = -(mod_efinfo[chn] & 0x0f); }
                     else
                       { i = (mod_efinfo[chn] >> 4) & 0x0f; }
                     pmod_volume[chn] += i;
                     if (pmod_volume[chn] > 64) { pmod_volume[chn] = 64; }
                     if (pmod_volume[chn] < 0) { pmod_volume[chn] = 0; }
                     pmod_play[chn] |= 0x004;
                     break;
                   }
        } //switch modeffect
    } //for chn
}


//********************************************************
void MODPLAYER_MAIN (void)
{ int i;

  pmod_delay--;

  for (i = 0; i<mod_channels;i++) { pmod_play[i] = 0; }

  if (pmod_delay == 0)
    { pmod_delay = pmod_speed;
      count_next_row();
    }
  else
    {
      do_betweencmd();
    }
  play_line();
}


//********************************************************
int mod_start(void)
{ int i;
  if ((mod_playing & 2) || ( (mod_playing & 1) != 1)) { return(0); }

  mix_call_routine = MODPLAYER_MAIN;

  mod_globalvolume = 64;
  for (i=0;i<mod_channels;i++)
    if (mod_balance[i] < 16)
      {  mix_balance[i] = mod_balance[i]; }

/*  for (i = 0; i < 32; i++)
    mod_balance[i] = 0xff;
  mod_balance[1] = 8; */

  mod_playing |= 2;
//  set_mixbuffsize ( 22050 / ((pmod_tempo*4)/10) );

  pmod_delay = pmod_speed;
  pmod_row = 0;
  pmod_track = 0;
  pmod_pattern = mod_orders[pmod_track];
  return 0;
}



void mod_check_volumes()
{ int i;
  for (i = 0; i < mod_channels; i++)
    mix_setvolume (i,pmod_volume[i] * modvolume / 64);
}


void mod_jumptotrack(int track)
{ int i;
  for (i = 0; i < mod_channels; i++)
     mix_speed[i] = 0; // stop all

  pmod_delay = pmod_speed;
  pmod_row = 0;
  pmod_track = track;
  pmod_pattern = mod_orders[pmod_track];
}
